package math.visitors;

import math.BooleanEquality;
import math.Xor;
import static math.ExpressionBuilder.beq;
import static math.ExpressionBuilder.and;
import math.ArithmeticError;
import math.Rule;
import math.LogicalBooleanVariable;
import java.util.ArrayList;
import java.util.List;
import math.Add;
import math.And;
import math.ApplyRulesCollector;
import math.ArithmeticExpression;
import math.BooleanConstant;
import math.BooleanExpression;
import math.BooleanVariable;
import math.Cos;
import math.Evaluation;
import math.Exp;
import math.Expression;
import math.ExpressionVisitor;
import math.IntegerConstant;
import math.Ln;
import math.LogicalArithmeticVariable;
import math.Mult;
import math.Negation;
import math.Not;
import math.NumericLogicalVariable;
import math.Or;
import math.PI;
import math.Pow;
import math.Rater;
import math.RuleSet;
import math.Sin;
import math.Variable;

public class ApplyRulesVisitor implements ExpressionVisitor<ApplyRulesCollector> {

  public ApplyRulesVisitor(Rater rater, RuleSet ruleSet) {
    this.ruleSet = ruleSet;
    this.result = new ApplyRulesCollector(rater);
  }

  public ApplyRulesVisitor(Rater rater, Rule... rules) {
    this.ruleSet = new RuleSet(rules);
    this.result = new ApplyRulesCollector(rater);
  }

  @Override
  public ApplyRulesCollector visitAnd(And and) {
    ApplyRulesCollector args1 = and.getArg1().accept(new ApplyRulesVisitor(result.getRater(), ruleSet));
    ApplyRulesCollector args2 = and.getArg2().accept(new ApplyRulesVisitor(result.getRater(), ruleSet));
    for (Expression a1 : args1) {
      for (Expression a2 : args2) {
        BooleanExpression newAnd = and((BooleanExpression)a1, (BooleanExpression)a2);
        result.addAll(ruleSet.apply(newAnd));
      }
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitBooleanConstant(BooleanConstant booleanConstant) {
    result.addAll(ruleSet.apply(booleanConstant));
    return result;
  }

  @Override
  public ApplyRulesCollector visitBooleanVariable(BooleanVariable booleanVariable) {
    result.addAll(ruleSet.apply(booleanVariable));
    return result;
  }

  @Override
  public ApplyRulesCollector visitNot(Not not) {
    ApplyRulesCollector args = not.getArgument().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression a : args) {
      BooleanExpression newNot = Not.createNot((BooleanExpression)a);
      result.addAll(ruleSet.apply(newNot));
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitOr(Or or) {
    ApplyRulesCollector args1 = or.getArg1().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    ApplyRulesCollector args2 = or.getArg2().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression a1 : args1) {
      for (Expression a2 : args2) {
        BooleanExpression newOr = Or.createOr((BooleanExpression)a1, (BooleanExpression)a2);
        result.addAll(ruleSet.apply(newOr));
      }
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitBooleanEquality(BooleanEquality booleanEquality) {
    ApplyRulesCollector args1 = booleanEquality.getArg1().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    ApplyRulesCollector args2 = booleanEquality.getArg2().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression a1 : args1) {
      for (Expression a2 : args2) {
        BooleanExpression newBeq = beq((BooleanExpression)a1, (BooleanExpression)a2);
        result.addAll(ruleSet.apply(newBeq));
      }
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitXor(Xor xor) {
    ApplyRulesCollector args1 = xor.getArg1().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    ApplyRulesCollector args2 = xor.getArg2().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression a1 : args1) {
      for (Expression a2 : args2) {
        BooleanExpression newXor = Xor.createXor((BooleanExpression)a1, (BooleanExpression)a2);
        result.addAll(ruleSet.apply(newXor));
      }
    }
    return result;
  }

  private RuleSet ruleSet;
  
  private ApplyRulesCollector result;
  
  @Override
  public ApplyRulesCollector visitAdd(Add add) {
    ApplyRulesCollector addends1 = add.getAddend1().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    ApplyRulesCollector addends2 = add.getAddend2().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression a1 : addends1) {
      for (Expression a2 : addends2) {
        ArithmeticExpression newAdd = Add.createAdd((ArithmeticExpression)a1, (ArithmeticExpression)a2);
        result.addAll(ruleSet.apply(newAdd));
      }
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitCos(Cos cos) {
    ApplyRulesCollector args = cos.getArgument().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression a : args) {
      ArithmeticExpression newCos = Cos.createCos((ArithmeticExpression)a);
      result.addAll(ruleSet.apply(newCos));
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitEvaluation(Evaluation evaluation) {
    List<Expression> tmp = new ArrayList<Expression>();
    tmp.addAll(ruleSet.apply(evaluation));
    while (!tmp.isEmpty()){
      Expression ae = tmp.remove(0);
      if (!result.contains(ae)){
        result.add(ae);
        tmp.addAll(ae.accept(new ApplyRulesVisitor(result.getRater(),ruleSet)).elements());
      }
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitExp(Exp exp) {
    ApplyRulesCollector args = exp.getArgument().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression a : args) {
      ArithmeticExpression newExp = Exp.createExp((ArithmeticExpression)a);
      result.addAll(ruleSet.apply(newExp));
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitIntegerConstant(IntegerConstant value) {
    result.addAll(ruleSet.apply(value));
    return result;
  }

  @Override
  public ApplyRulesCollector visitLn(Ln ln) {
    ApplyRulesCollector args = ln.getArgument().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression a : args) {
      ArithmeticExpression newLn = Ln.createLn((ArithmeticExpression)a);
      result.addAll(ruleSet.apply(newLn));
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitLogicalArithmeticVariable(LogicalArithmeticVariable logicalVariable) {
    result.addAll(ruleSet.apply(logicalVariable));
    return result;
  }

  @Override
  public ApplyRulesCollector visitLogicalBooleanVariable(LogicalBooleanVariable logicalVariable) {
    result.addAll(ruleSet.apply(logicalVariable));
    return result;
  }

  @Override
  public ApplyRulesCollector visitMult(Mult mult) {
    ApplyRulesCollector factors1 = mult.getFactor1().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    ApplyRulesCollector factors2 = mult.getFactor2().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression f1 : factors1) {
      for (Expression f2 : factors2) {
        ArithmeticExpression newMult = Mult.createMult((ArithmeticExpression)f1, (ArithmeticExpression)f2);
        result.addAll(ruleSet.apply(newMult));
      }
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitNegation(Negation negation) {
    ApplyRulesCollector args = negation.getArgument().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression a : args) {
      ArithmeticExpression newNeg = Negation.createNegation((ArithmeticExpression)a);
      result.addAll(ruleSet.apply(newNeg));
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitNumericalLogicalVariable(
      NumericLogicalVariable numericLogicalVariable) {
    result.addAll(ruleSet.apply(numericLogicalVariable));
    return result;
  }

  @Override
  public ApplyRulesCollector visitPI(PI pi) {
    result.addAll(ruleSet.apply(pi));
    return result;
  }

  @Override
  public ApplyRulesCollector visitPow(Pow pow) {
    ApplyRulesCollector bases = pow.getBase().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    ApplyRulesCollector exponents = pow.getExponent().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression b: bases){
      for (Expression e: exponents){
        ArithmeticExpression newPow = Pow.createPow((ArithmeticExpression)b, (ArithmeticExpression)e);
        result.addAll(ruleSet.apply(newPow));
      }
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitSin(Sin sin) {
    ApplyRulesCollector args = sin.getArgument().accept(new ApplyRulesVisitor(result.getRater(),ruleSet));
    for (Expression a : args) {
      ArithmeticExpression newSin = Sin.createSin((ArithmeticExpression)a);
      result.addAll(ruleSet.apply(newSin));
    }
    return result;
  }

  @Override
  public ApplyRulesCollector visitVariable(Variable var) {
    result.addAll(ruleSet.apply(var));
    return result;
  }

  @Override
  public ApplyRulesCollector visitArithmeticError(
      ArithmeticError arithmeticError) {
    result.addAll(ruleSet.apply(arithmeticError));
    return result;
  }

}
